import { eval_helper } from './eval_helper'
import { domain_helper } from './domain_helper'

function eval_modifiers(modifiers, { editable, record, context }) {
  if (typeof modifiers === 'boolean') {
    return modifiers
  } else if (typeof modifiers === 'number') {
    return modifiers ? true : false
  } else if (typeof modifiers === 'string') {
    const me = Number(modifiers)
    if (Number.isNaN(me)) {
      console.log('eval_modifiers. doing', modifiers, typeof modifiers)
      throw 'error modifiers string: ' + JSON.stringify(modifiers)
    } else {
      return modifiers ? true : false
    }
  } else if (typeof modifiers === 'function') {
    console.log('eval_modifiers. doing', modifiers, typeof modifiers)
    throw 'error modifiers: ' + JSON.stringify(modifiers)
  } else if (Array.isArray(modifiers)) {
    // console.log('eval_modifiers. Array', modifiers, typeof modifiers)
    return eval_helper.eval_array(modifiers, { editable, record, context })
  } else if (typeof modifiers === 'undefined') {
    console.log('eval_modifiers. undefined', modifiers, typeof modifiers)
    return modifiers
  } else if (typeof modifiers === 'object') {
    // console.log('eval_modifiers. object', modifiers)
    return eval_helper.eval_object(modifiers, { editable, record, context })
    // throw 'error modifiers: object' + modifiers
  } else {
    console.log('eval_modifiers. doing', modifiers, typeof modifiers)
    throw 'error modifiers: ' + JSON.stringify(modifiers)
  }
}

const node_helper = {
  check_groups(groups, { user_groups }) {
    if (!groups) {
      return true
    }

    // console.log(groups, user_groups)

    // if (groups === 'base.group_no_one') {
    //   console.log(groups, user_groups)
    // }

    const res = groups.split(',').reduce((acc, cur) => {
      return acc && user_groups.includes(cur)
    }, true)

    return res
  },

  check_modifiers(modifiers, kws) {
    // const { context, record, editable } = kws

    if (typeof modifiers === 'function') {
      console.log('modifiers, is function. todo')
      throw 'modifiers is function. todo'
      // return modifiers(kws)
    }
    return modifiers
  },

  check_readonly(nodeinfo, { meta, editable, record, context }) {
    // nodeinfo 中:
    // required: 用于字段编辑. 依次取自 nodeinfo,nodeinfo.attrs, meta
    // readonly: 用于字段编辑. 依次取自 nodeinfo,nodeinfo.attrs, meta
    // meta.readonly/required 一定不是函数
    // meta.readonly 结合 meta.states 控制 write 函数的 value
    // nodeinfo.required/readonly 若为函数 仅需要 context, editable
    // nodeinfo.attrs.required/readonly 一定是函数 且需要 record
    //
    // 第一次 无 record时,
    //  required = false, readonly=true
    // 第二次 有 record, 只读, 未编辑时:
    //  required = false, readonly=true
    // 第三次 有 record, 编辑时: 依次取:
    //  1. nodeinfo.attrs.required/readonly
    //  2. nodeinfo.required/readonly
    //  3. meta.readonly/required

    if (!editable) {
      return true
    }

    function check_meta() {
      if (!('state' in (record || {}))) {
        return meta.readonly
      }
      if (!('states' in meta)) {
        return meta.readonly
      }
      if (!(record.state in meta.states)) {
        return meta.readonly
      }
      const dictok = meta.states[record.state].reduce((acc, cur) => {
        acc[cur[0]] = cur[1]
        return acc
      }, {})

      if (!('readonly' in dictok)) {
        return meta.readonly
      }

      return dictok.readonly
    }

    const meta_val = check_meta()
    if (meta_val) {
      return meta_val
    }

    const modifiers = 'readonly'

    if (modifiers in nodeinfo) {
      // console.log('check_readonly. todo', nodeinfo[modifiers])

      return eval_modifiers(nodeinfo[modifiers], { record, context })
    }

    const { attrs = {} } = nodeinfo

    if (modifiers in attrs) {
      return eval_modifiers(attrs[modifiers], { record, context })

      // return this.check_modifiers(attrs[modifiers], { record })
    }
    return undefined
  },

  check_required(nodeinfo, { meta, editable, record, context }) {
    if (!editable) {
      return false
    }

    const modifiers = 'required'

    const meta_val = meta[modifiers]
    if (meta_val) {
      // console.log('check_required1. todo', nodeinfo)
      return eval_modifiers(meta_val, { record, context })
    }

    if (modifiers in nodeinfo) {
      // console.log('check_required2. todo', nodeinfo)

      return eval_modifiers(nodeinfo[modifiers], { record, context })
    }

    const { attrs = {} } = nodeinfo

    if (modifiers in attrs) {
      // console.log('check_required3. todo', nodeinfo)
      return eval_modifiers(attrs[modifiers], { record, context })
    }

    return undefined
  },

  get_domain(nodeinfo, { ...kws }) {
    // const { env, meta, record, context } = kws
    const { meta } = kws
    const modifiers = 'domain'
    if (!(modifiers in meta)) {
      return
    }

    function domain_get() {
      const domain_raw =
        modifiers in nodeinfo ? nodeinfo[modifiers] : meta[modifiers]

      return domain_helper.domain_maker(domain_raw, { ...kws })
    }

    const domain_raw = domain_get()

    return domain_raw
  },

  check_invisible2(modifiers, { editable, record, context }) {
    // console.log('check_invisible2', modifiers, editable, record, context)
    const res = eval_modifiers(modifiers, { editable, record, context })
    // console.log('check_invisible2 ok', res)
    return res
  },

  check_invisible(nodeinfo, kws = {}) {
    const { for_view, user_groups, editable, record, context } = kws

    if ('groups' in nodeinfo) {
      if (!this.check_groups(nodeinfo.groups, { user_groups })) {
        return true
      }
    }

    if ('invisible' in nodeinfo) {
      // 全局 editabl, context 控制显示隐藏.
      // console.log('check_invisible. todo', nodeinfo, kws)

      return this.check_invisible2(nodeinfo.invisible, { editable, context })
    }

    if ('column_invisible' in nodeinfo) {
      // 全局 editabl, context 控制显示隐藏.
      // console.log('check_invisible. todo', nodeinfo, kws)

      return this.check_invisible2(nodeinfo.column_invisible, {
        editable,
        context
      })
    }

    const { attrs = {} } = nodeinfo

    if (['form.data', 'form.view', 'o2m.form'].includes(for_view)) {
      if ('invisible' in attrs) {
        // 无数据时  所有 invisible  一律不显示
        // 有数据后, 再计算 invisible

        // 'form.data' : 'form.view'
        if (for_view === 'form.view') {
          return true
        } else if (['form.data', 'o2m.form'].includes(for_view)) {
          // console.log('check_invisible. todo', nodeinfo, attrs.invisible)

          return this.check_invisible2(attrs.invisible, { record, editable })
        } else {
          return true
        }
      } else {
        return false
      }
    } else if (['o2m.tree'].includes(for_view)) {
      if ('column_invisible' in attrs) {
        // console.log('check_invisible =================', nodeinfo, attrs, kws)
        // console.log('check_invisible. todo', nodeinfo, attrs.column_invisible)

        return this.check_invisible2(attrs.column_invisible, {
          record,
          editable
        })
      } else {
        return false
      }
    } else if (['tree'].includes(for_view)) {
      return false
    } else if (['search'].includes(for_view)) {
      return false
    } else {
      return true
    }

    // return false
  },

  context_get(ctx, kws = {}) {
    // const { context, record } = kws

    // console.log(' context_get ', ctx, kws)

    const ctx2 = Object.keys(ctx).reduce((acc, cur) => {
      const todo = ctx[cur]
      if (['string', 'number', 'boolean'].includes(typeof todo)) {
        acc[cur] = todo
      } else if (Array.isArray(todo)) {
        acc[cur] = todo
      } else if (typeof todo === 'object') {
        acc[cur] = eval_helper.eval_object(todo, kws)
      } else {
        // todo :
        // default_currency_id: currency_id or company_currency_id,

        console.log('context_get. doing', ctx, cur, todo)
        throw 'error, context_get' + JSON.stringify(ctx)
      }

      return acc
    }, {})

    // console.log(' context_get 2', ctx2)

    return ctx2
  },

  parse_node(node, kwargs = {}) {
    const { record, record_onchange, editable, user_groups } = kwargs
    const { for_view, module } = kwargs

    // if (for_view === 'search') {
    //   console.log('parse_node', for_view, node, kwargs)
    // }
    const { env, fields, metadata, context } = kwargs
    const { last_nodename = 'sheet', is_group_children } = kwargs
    const { is_one_row_children } = kwargs

    const self = this

    function split_name(str) {
      const ns = str.split('_')
      const [tagname, ...tagname22] = ns
      const tagname2 = tagname22.join('_')
      const fieldname = tagname2.split('__')[0]
      return [tagname, fieldname]
    }

    function my_check_readonly(nodeinfo, meta) {
      const kws = { env, meta, editable, record, context }
      return self.check_readonly(nodeinfo, kws)
    }

    function my_check_required(nodeinfo, meta) {
      const kws = { env, meta, editable, record, context }
      return self.check_required(nodeinfo, kws)
    }

    function my_get_domain(nodeinfo, meta) {
      const meta2 = { ...meta, ...(fields[meta.name] || {}) }
      const kws = { env, meta: meta2, record: record || {}, context }

      return self.get_domain(nodeinfo, kws)
    }

    function my_check_invisible(nodeinfo) {
      const kws = { env, for_view, user_groups, editable, record, context }
      return self.check_invisible(nodeinfo, kws)
    }

    function my_context(nodeinfo) {
      const kws = { env, record: record_onchange || {}, context }
      return self.context_get(nodeinfo, kws)
    }

    function my_invisible_for_o2m_tree(nodeinfo) {
      if (for_view === 'o2m.tree') {
        const { attrs = {} } = nodeinfo
        if ('invisible' in attrs) {
          return { ...attrs }
        }
      }
    }

    return Object.keys(node).reduce((acc, nodename) => {
      const nodeinfo = node[nodename]
      if (is_one_row_children && typeof nodeinfo !== 'string') {
        nodeinfo.one_row = 1
      }

      const [tagname, fname] = split_name(nodename)
      // if (for_view === 'search') {
      //   console.log('parse_node11', tagname, fname)
      // }

      const all_tags = [
        'card',
        'group',
        'subgroup',
        'notebook',
        'page',
        'widget',
        'button'
      ]
      if (tagname === 'unknow') {
        //
      } else if (for_view === 'search' && tagname === 'field') {
        const invisible = my_check_invisible(nodeinfo)

        if (!invisible) {
          const meta = metadata[fname]

          const nodeinfo2 = { ...nodeinfo }
          delete nodeinfo2.groups
          acc[nodename] = {
            meta,
            name: fname,
            string: meta.string,
            ...nodeinfo2,
            tagname,
            nodename
          }
        }
      } else if (for_view === 'search' && tagname === 'groupby') {
        acc[nodename] = {
          ...nodeinfo,
          tagname,
          nodename
        }
      } else if (for_view === 'search' && tagname === 'filter') {
        const { string, ...nodeinfo2 } = nodeinfo

        const nodeinfo3 = Object.keys(nodeinfo2).reduce((acc, cur) => {
          acc[cur] = { ...nodeinfo2[cur], name: cur }
          return acc
        }, {})
        acc[nodename] = {
          string,
          children: { ...nodeinfo3 },
          tagname,
          nodename
        }
      } else if (tagname === 'field') {
        const invisible = my_check_invisible(nodeinfo)
        // console.log('parse_node12', invisible)
        if (!invisible) {
          // console.log('field,vis', last_nodename, nodename, nodeinfo)

          // string: 用于字段label. 依次取自 nodeinfo, fieldinfo, meta
          // meta: 取自 odoo.metadata
          // widget: 控制前端组件. 取自 nodeinfo,
          // fieldinfo 中 不该再有其他信息了
          // nodeinfo 中 可增加一些控制属性. 如宽高背景色等
          //

          const fieldinfo = fields[fname] || {}
          const meta = metadata[fname]
          const nodeinfo2 = { ...nodeinfo }

          delete nodeinfo2.invisible
          delete nodeinfo2.groups
          delete nodeinfo2.attrs
          delete nodeinfo2.readonly
          delete nodeinfo2.required

          const label_nodes = Object.keys(nodeinfo2)
            .filter(item => item.split('_')[0] === 'label')
            .filter(item => my_check_invisible(nodeinfo2[item]))

          const mylabel = label_nodes

          label_nodes.forEach(item => {
            delete nodeinfo2[item]
          })

          const readonly = my_check_readonly(nodeinfo, meta)
          const required = my_check_required(nodeinfo, meta)

          const domain = my_get_domain(nodeinfo, meta)

          const attrs = my_invisible_for_o2m_tree(nodeinfo)

          const ctx =
            'context' in nodeinfo ? my_context(nodeinfo.context) : undefined

          acc[nodename] = {
            meta,
            name: meta.name,
            string: meta.string,
            ...fieldinfo,
            ...nodeinfo2,
            readonly,
            required,
            ...(domain ? { domain: domain } : {}),
            ...(attrs ? { attrs: attrs } : {}),
            ...(ctx ? { context: ctx } : {}),
            tagname,
            nodename
          }
        } else {
          // console.log('field,inv', last_nodename, nodename, nodeinfo)
        }
      } else if (typeof nodeinfo === 'string') {
        // console.log('tag:', last_nodename, nodename, nodeinfo)
        const attr2 = {
          text: nodeinfo
        }
        if (tagname === 'widget') {
          attr2.name = fname
          attr2.one_row = is_one_row_children
        }

        if (is_one_row_children) {
          attr2.one_row = is_one_row_children
        }

        acc[nodename] = { ...attr2, tagname, nodename }
      } else if (tagname === 'label') {
        // console.log('label,', nodeinfo, kwargs)
        const invisible = my_check_invisible(nodeinfo)

        if (!invisible) {
          const nodeinfo2 = { ...nodeinfo }
          delete nodeinfo2.invisible
          delete nodeinfo2.groups
          delete nodeinfo2.attrs

          const path = `${last_nodename}.${nodename}`

          const children = node_helper.parse_node(nodeinfo2, {
            ...kwargs,
            last_nodename: path
          })

          acc[nodename] = { ...nodeinfo2, children, tagname, nodename }
        }
      } else if (tagname === 'button') {
        const invisible = my_check_invisible(nodeinfo)
        // console.log(invisible, nodeinfo)

        if (!invisible) {
          const { ...nodeinfo2 } = nodeinfo
          delete nodeinfo2.invisible
          delete nodeinfo2.groups
          delete nodeinfo2.attrs

          if (nodeinfo2.type === 'action') {
            // console.log('btn,', nodeinfo2.name, module)
            if (nodeinfo2.name.split('.').length === 1) {
              nodeinfo2.name = `${module}.${nodeinfo2.name}`
            }
          }

          const [nodeinfo3, nodeinfo4] = Object.keys(nodeinfo2).reduce(
            (acc2, cur2) => {
              if (['widget', 'field'].includes(cur2.split('_')[0])) {
                acc2[0][cur2] = nodeinfo2[cur2]
              } else {
                acc2[1][cur2] = nodeinfo2[cur2]
              }

              return acc2
            },
            [{}, {}]
          )

          const path = `${last_nodename}.${nodename}`

          const children = node_helper.parse_node(nodeinfo3, {
            ...kwargs,
            last_nodename: path
          })

          acc[nodename] = { ...nodeinfo4, children, tagname, nodename }
        } else {
          // console.log('field,inv', last_nodename, nodename, nodeinfo)
        }
      } else if (!all_tags.includes(tagname)) {
        console.log(' parse_node', last_nodename, nodename)
        throw `error parse_node: ${last_nodename}.${nodename}`
        //
      } else {
        const { attr = {}, ...nodeinfo2 } = nodeinfo
        const invisible = my_check_invisible(attr)

        if (!invisible) {
          const path = `${last_nodename}.${nodename}`

          const children = node_helper.parse_node(nodeinfo2, {
            ...kwargs,
            last_nodename: path,
            is_group_children: is_group_children || tagname === 'group',
            is_one_row_children: fname === 'one_row' || is_one_row_children
          })

          // // attr 可能有  string, name, id, widget
          // // string, 可用于显示文本.
          // // widget, 用于 前端自定义组件
          // // 其他无实际用处
          const attr2 = { ...attr }
          delete attr2.invisible
          delete attr2.groups
          delete attr2.attrs
          if (tagname === 'widget') {
            attr2.name = fname
          }
          // // tagname 用于 前端判断 通用组件
          acc[nodename] = {
            ...attr2,
            children,
            tagname:
              tagname === 'group' && is_group_children ? 'subgroup' : tagname,
            nodename
          }
        } else {
          // console.log('tag,inv', last_nodename, nodename, nodeinfo)
        }
      }

      return acc
    }, {})
  }
}

export function parse_node(node, kwargs = {}) {
  console.log('parse_node:', node, kwargs)
  const res = node_helper.parse_node(node, kwargs)
  console.log('parse_node ok', res)
  return res
}

export const fields_helper = {
  merge_fields(dst = {}, src = {}) {
    return Object.keys({ ...dst, ...src }).reduce((acc, fld) => {
      const indst = dst[fld] || {}
      const insrc = src[fld] || {}

      const ok = { ...indst, ...insrc }
      if ('fields' in indst && 'fields' in insrc) {
        ok.fields = this.merge_fields(indst.fields, insrc.fields)
      }
      acc[fld] = ok
      return acc
    }, {})
  },

  fields_get_in_sheet(sheet) {
    function get_node_name(str) {
      if (!str[0] === '_') return false
      const tag = str.split('_')[1]
      return tag
    }

    // treeview 格式
    // sheet:{
    //   field_name: {},
    //   field_name__other_char: {},
    // }
    //
    // formview 格式
    // sheet:{
    //   field_name: {},
    //   field_name__other_char: {},
    //   tagname_other_char: {
    //     attr: {},
    //     field_name: {},
    //     field_name__other_char: {},
    //   },
    // }
    //

    const self = this

    function split_name(str) {
      const ns = str.split('_')
      const [tagname, ...tagname22] = ns
      const tagname2 = tagname22.join('_')
      const fieldname = tagname2.split('__')[0]
      return [tagname, fieldname]
    }

    function find_field(node) {
      if (!node) {
        return {}
      }

      if (typeof node !== 'object') {
        return {}
      }

      return Object.keys(node).reduce((acc, cur) => {
        const [tagname, fname] = split_name(cur)

        if (tagname === 'field') {
          const meta = { ...node[cur] }
          const me = { [fname]: { ...meta } }
          acc = self.merge_fields(acc, me)
        } else if (tagname === 'filter') {
          //
        } else {
          const children = find_field(node[cur])
          acc = { ...acc, ...children }
        }

        return acc
      }, {})
    }

    const fields = find_field(sheet)
    return fields
  },

  fields_get_in_views(views) {
    const fs_in_views = Object.keys(views).reduce((acc, viewtype) => {
      const vi = views[viewtype]
      const { arch = {} } = vi

      // const { fields: fs_vi = {} } = vi
      // const { fields: fs_arch = {} } = arch
      // acc[`${viewtype}.vi`] = fs_vi
      // acc[`${viewtype}.arch`] = fs_arch

      const { button_box = {}, header = {}, sheet = {}, kanban = {} } = arch

      acc[`${viewtype}.box`] = this.fields_get_in_sheet(button_box)
      acc[`${viewtype}.header`] = this.fields_get_in_sheet(header)
      acc[`${viewtype}.sheet`] = this.fields_get_in_sheet(sheet)
      acc[`${viewtype}.kanban`] = this.fields_get_in_sheet(kanban)
      return acc
    }, {})

    const fields = Object.keys(fs_in_views).reduce((acc, cur) => {
      const fs = fs_in_views[cur]

      Object.keys(fs).forEach(fld => {
        const meta = fs[fld] || {}

        const old = acc[fld] || {}
        acc[fld] = { ...old }

        if (meta.fields) {
          const fields2 = { ...meta.fields }
          if (!('display_name' in fields2)) {
            fields2.display_name = {}
          }
          const me = { [fld]: { fields: fields2 } }
          acc = this.merge_fields(acc, me)
        }

        if (meta.views) {
          const views2 = Object.keys(meta.views).reduce((acc2, cur2) => {
            acc2[`${cur}.${cur2}`] = meta.views[cur2]
            return acc2
          }, {})

          const me_fields = this.fields_get_in_views(views2)

          if (Object.keys(me_fields).length) {
            const me = { [fld]: { fields: { ...me_fields } } }
            acc = this.merge_fields(acc, me)
          }
        }
      })

      return acc
    }, {})

    return fields
  }
}
